﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024-2025 LiLong                                              **
**  This file is part of OpenVisaApplication.                                   **
**                                                                              **
**  OpenVisaApplication is free software: you can redistribute it and/or modify **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  OpenVisaApplication is distributed in the hope that it will be useful,      **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with OpenVisaApplication. If not, see <https://www.gnu.org/licenses/>.**
**********************************************************************************/
#include "TestView.h"
#include "../Excel/Excel.h"
#include "TestModel.h"

#include <AppCore/Device.h>
#include <AppCore/StringEscape.h>
#include <QCustomUi/QCtmHeaderView.h>
#include <QCustomUi/QCtmMessageBox.h>

#include <QApplication>
#include <QClipboard>
#include <QCryptographicHash>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QMenu>
#include <QStandardPaths>
#include <QTimer>
#include <QToolBar>

struct TestView::Impl
{
    std::shared_ptr<AppCore::Device> device;
    TestModel* model;
    QToolBar* toolBar { nullptr };
    QAction* startAction { nullptr };
    QAction* receiveAction { nullptr };
    QAction* queryAction { nullptr };
    QAction* autoQueryAction { nullptr };
    QAction* clearResultAction { nullptr };
    QAction* importAction { nullptr };
    QAction* exportAction { nullptr };
    QTimer saveCacheTimer;
    QString ioPathCache;
    QAction* pasteAction;
    QAction* copyAction;
    QAction* deleteAction;
    inline Impl(std::shared_ptr<AppCore::Device> d) : device(d) {}
};

TestView::TestView(std::shared_ptr<AppCore::Device> device, QWidget* parent) : QWidget(parent), m_impl(std::make_unique<Impl>(device))
{
    ui.setupUi(this);
    setAttribute(Qt::WA_StyledBackground);
    setWindowTitle(device->name());
    ui.tableView->setModel(m_impl->model = new TestModel(this));
    auto vHeader = new QCtmHeaderView(Qt::Vertical, this);
    vHeader->setObjectName("vHeader");
    ui.tableView->setVerticalHeader(vHeader);
    initToolBar();
    Excel::load(m_impl->model, cachePath());
    m_impl->saveCacheTimer.setSingleShot(true);
    m_impl->saveCacheTimer.setInterval(500);
    connect(&m_impl->saveCacheTimer, &QTimer::timeout, this, &TestView::onSaveCache);
    connect(
        m_impl->model, &TestModel::dataChanged, std::bind_front(static_cast<void (QTimer::*)()>(&QTimer::start), &m_impl->saveCacheTimer));
    connect(
        m_impl->model, &TestModel::modelReset, std::bind_front(static_cast<void (QTimer::*)()>(&QTimer::start), &m_impl->saveCacheTimer));
    if (!device->object()->connected())
    {
        m_impl->startAction->setEnabled(false);
        m_impl->autoQueryAction->setEnabled(false);
    }
    m_impl->pasteAction  = ui.tableView->addAction(QIcon(":/OpenVisaApplication/Resources/Paste.svg"), u8"粘贴", QKeySequence::Paste);
    m_impl->copyAction   = ui.tableView->addAction(QIcon(":/OpenVisaApplication/Resources/Copy.svg"), u8"复制", QKeySequence::Copy);
    m_impl->deleteAction = ui.tableView->addAction(QIcon(":/OpenVisaApplication/Resources/Delete.svg"), u8"删除", QKeySequence::Delete);

    m_impl->copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    m_impl->deleteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    connect(m_impl->pasteAction, &QAction::triggered, this, &TestView::onPasteAction);
    connect(m_impl->copyAction, &QAction::triggered, this, &TestView::onCopyAction);
    connect(m_impl->deleteAction, &QAction::triggered, this, &TestView::onDeleteAction);

    ui.tableView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui.tableView, &QCtmTableView::customContextMenuRequested, this, &TestView::onCustomContextMenuRequested);
}

TestView::~TestView()
{
    if (m_impl->device->object()->connected())
        m_impl->device->object()->close();
}

std::shared_ptr<AppCore::Device> TestView::device() const { return m_impl->device; }

void TestView::onStartAction()
{
    auto cmd = currentCommand();
    if (!cmd)
        return;
    if (m_impl->autoQueryAction->isChecked() && cmd->contains("?"))
        query(*cmd);
    else
        send(*cmd);
}

void TestView::onReceiveAction()
{
    try
    {
        auto ret = m_impl->device->object()->readAll();
        m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)),
                               AppCore::StringEscape::decode(QString::fromStdString(ret)),
                               Qt::EditRole);
    }
    catch (const std::exception& e)
    {
        m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)),
                               QString::fromLocal8Bit(e.what()),
                               Qt::EditRole);
    }
}

void TestView::onQueryAction()
{
    auto cmd = currentCommand();
    if (!cmd)
        return;
    query(*cmd);
}

void TestView::onSaveCache() { Excel::save(m_impl->model, cachePath()); }

void TestView::onClearResultAction()
{
    if (QCtmMessageBox::question(this, u8"提示", u8"是否清空所有返回值？") == QCtmMessageBox::Yes)
    {
        m_impl->model->clearResults();
    }
}

void TestView::onExportAction()
{
    auto f = QFileDialog::getSaveFileName(this, u8"导出", m_impl->ioPathCache, "Excel File (*.xlsx)");
    if (f.isEmpty())
        return;
    QFileInfo info(f);
    m_impl->ioPathCache = info.dir().path();
    if (Excel::save(m_impl->model, f))
    {
        QCtmMessageBox::information(this, u8"提示", u8"导出成功。");
    }
    else
    {
        QCtmMessageBox::critical(this, u8"错误", u8"导出失败。");
    }
}

void TestView::onImportAction()
{
    auto f = QFileDialog::getOpenFileName(this, u8"导入", m_impl->ioPathCache, "Excel File (*.xlsx)");
    if (f.isEmpty())
        return;
    QFileInfo info(f);
    m_impl->ioPathCache = info.dir().path();
    if (Excel::load(m_impl->model, f))
    {
        QCtmMessageBox::information(this, u8"提示", u8"导入成功。");
    }
    else
    {
        QCtmMessageBox::critical(this, u8"错误", u8"导入失败。");
    }
}

void TestView::onPasteAction()
{
    auto index = ui.tableView->currentIndex();
    if (static_cast<TestModel::Columns>(index.column()) != TestModel::Columns::Command)
        return;
    auto txt = qApp->clipboard()->text();
    if (txt.isEmpty())
        return;
    m_impl->model->paste(index.row(), txt);
}

void TestView::onCopyAction()
{
    auto indexes = ui.tableView->selectionModel()->selectedIndexes();
    if (indexes.isEmpty())
        return;
    m_impl->model->copy(indexes);
}

void TestView::onCustomContextMenuRequested(const QPoint& pos)
{
    auto index = ui.tableView->indexAt(pos);
    if (!index.isValid())
        return;
    QMenu menu(this);
    menu.addAction(m_impl->copyAction);
    if (static_cast<TestModel::Columns>(index.column()) == TestModel::Columns::Command)
        menu.addAction(m_impl->pasteAction);
    m_impl->pasteAction->setEnabled(!qApp->clipboard()->text().isEmpty());
    menu.addSeparator();
    menu.addAction(m_impl->deleteAction);
    menu.exec(QCursor::pos());
    m_impl->pasteAction->setEnabled(true);
}

void TestView::onDeleteAction()
{
    auto indexes = ui.tableView->selectionModel()->selectedIndexes();
    if (indexes.isEmpty())
        return;
    m_impl->model->remove(indexes);
}

QString TestView::cachePath() const
{
    QCryptographicHash alg(QCryptographicHash::Md5);
    alg.addData(this->windowTitle().toUtf8());
    QString name = alg.result().toHex();
    return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/Cache/" + name + ".xlsx";
}

void TestView::send(const std::string& cmd)
{
    m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)), "", Qt::EditRole);
    try
    {
        m_impl->device->object()->send(cmd);
    }
    catch (const std::exception& e)
    {
        m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)),
                               QString::fromLocal8Bit(e.what()),
                               Qt::EditRole);
    }
}

void TestView::query(const std::string& cmd)
{
    m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)), "", Qt::EditRole);
    try
    {
        auto ret = m_impl->device->object()->query(cmd);
        m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)),
                               AppCore::StringEscape::decode(QString::fromStdString(ret)),
                               Qt::EditRole);
    }
    catch (const std::exception& e)
    {
        m_impl->model->setData(ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Result)),
                               QString::fromLocal8Bit(e.what()),
                               Qt::EditRole);
    }
}

std::optional<std::string> TestView::currentCommand()
{
    if (auto w = qApp->focusWidget(); w)
        w->clearFocus();
    if (!m_impl->device->object()->connected())
        return std::nullopt;
    if (!ui.tableView->currentIndex().isValid())
    {
        QCtmMessageBox::warning(this, u8"警告", u8"请选中要发送的指令。");
        return std::nullopt;
    }
    QByteArray cmd = ui.tableView->currentIndex().siblingAtColumn(static_cast<int>(TestModel::Columns::Command)).data().toString().trimmed().toUtf8();
    auto en  = AppCore::StringEscape::encode(cmd);
    if (!en)
    {
        QCtmMessageBox::warning(this, u8"警告", en.error());
        return std::nullopt;
    }
    cmd = en.value();
    if (cmd.isEmpty())
    {
        QCtmMessageBox::warning(this, u8"警告", u8"指令为空。");
        return std::nullopt;
    }
    return cmd.toStdString();
}

void TestView::initToolBar()
{
    ui.toolbarLayout->addWidget(m_impl->toolBar = new QToolBar(this));

    m_impl->startAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/Start.svg"), "");
    m_impl->startAction->setToolTip(u8"发送(F5)");
    m_impl->startAction->setShortcut(QKeySequence(Qt::Key_F5));

    m_impl->receiveAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/Receive.svg"), "");
    m_impl->receiveAction->setToolTip(u8"接收");
    m_impl->receiveAction->setEnabled(false);

    m_impl->queryAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/Query.svg"), "");
    m_impl->queryAction->setToolTip(u8"查询(F6)");
    m_impl->queryAction->setEnabled(false);
    m_impl->queryAction->setShortcut(QKeySequence(Qt::Key_F6));

    m_impl->autoQueryAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/AutoQuery.svg"), "");
    m_impl->autoQueryAction->setToolTip(u8"查询指令自动读取返回值");
    m_impl->autoQueryAction->setCheckable(true);
    m_impl->autoQueryAction->setChecked(true);

    m_impl->toolBar->addSeparator();

    m_impl->clearResultAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/Clear.svg"), "");
    m_impl->clearResultAction->setToolTip(u8"清空返回值");

    m_impl->toolBar->setFocusPolicy(Qt::StrongFocus);

    QWidget* spacer = new QWidget;
    spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    m_impl->toolBar->addWidget(spacer);
    m_impl->importAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/ImportExcel.svg"), "");
    m_impl->exportAction = m_impl->toolBar->addAction(QIcon(":/OpenVisaApplication/Resources/ExportExcel.svg"), "");

    connect(m_impl->exportAction, &QAction::triggered, this, &TestView::onExportAction);
    connect(m_impl->importAction, &QAction::triggered, this, &TestView::onImportAction);
    connect(m_impl->startAction, &QAction::triggered, this, &TestView::onStartAction);
    connect(m_impl->receiveAction, &QAction::triggered, this, &TestView::onReceiveAction);
    connect(m_impl->queryAction, &QAction::triggered, this, &TestView::onQueryAction);
    connect(m_impl->autoQueryAction, &QAction::toggled, m_impl->queryAction, &QAction::setDisabled);
    connect(m_impl->autoQueryAction, &QAction::toggled, m_impl->receiveAction, &QAction::setDisabled);
    connect(m_impl->clearResultAction, &QAction::triggered, this, &TestView::onClearResultAction);
}
